/*
 * File    : TRHostTorrentImpl.java
 * Created : 26-Oct-2003
 * By      : stuff
 * 
 * Azureus - a Java Bittorrent client
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details ( see the LICENSE file ).
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package org.gudy.azureus2.core3.tracker.host.impl;

/**
 * @author parg
 *
 */

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import org.gudy.azureus2.core3.torrent.TOTorrent;
import org.gudy.azureus2.core3.torrent.TOTorrentException;
import org.gudy.azureus2.core3.tracker.host.TRHostException;
import org.gudy.azureus2.core3.tracker.host.TRHostPeer;
import org.gudy.azureus2.core3.tracker.host.TRHostTorrent;
import org.gudy.azureus2.core3.tracker.host.TRHostTorrentListener;
import org.gudy.azureus2.core3.tracker.host.TRHostTorrentRemovalVetoException;
import org.gudy.azureus2.core3.tracker.host.TRHostTorrentRequest;
import org.gudy.azureus2.core3.tracker.host.TRHostTorrentWillBeRemovedListener;
import org.gudy.azureus2.core3.tracker.server.TRTrackerServer;
import org.gudy.azureus2.core3.tracker.server.TRTrackerServerPeer;
import org.gudy.azureus2.core3.tracker.server.TRTrackerServerTorrent;
import org.gudy.azureus2.core3.tracker.server.TRTrackerServerTorrentStats;
import org.gudy.azureus2.core3.util.AEMonitor;
import org.gudy.azureus2.core3.util.Average;
import org.gudy.azureus2.core3.util.Debug;

public class TRHostTorrentHostImpl implements TRHostTorrent {
    private TRHostImpl host;
    private TRTrackerServer server;
    private TRTrackerServerTorrent server_torrent;
    private TOTorrent torrent;
    private long date_added;
    private int port;

    private List listeners_cow = new ArrayList();
    private List removal_listeners = new ArrayList();

    private int status = TS_STOPPED;
    private boolean persistent;
    private boolean passive;

    private long sos_uploaded;
    private long sos_downloaded;
    private long sos_bytes_in;
    private long sos_bytes_out;
    private long sos_announce;
    private long sos_scrape;
    private long sos_complete;

    private long last_uploaded;
    private long last_downloaded;
    private long last_bytes_in;
    private long last_bytes_out;
    private long last_announce;
    private long last_scrape;

    // average over 10 periods, update every period.

    private Average average_uploaded = Average.getInstance(TRHostImpl.STATS_PERIOD_SECS * 1000, TRHostImpl.STATS_PERIOD_SECS * 10);
    private Average average_downloaded = Average.getInstance(TRHostImpl.STATS_PERIOD_SECS * 1000, TRHostImpl.STATS_PERIOD_SECS * 10);
    private Average average_bytes_in = Average.getInstance(TRHostImpl.STATS_PERIOD_SECS * 1000, TRHostImpl.STATS_PERIOD_SECS * 10);
    private Average average_bytes_out = Average.getInstance(TRHostImpl.STATS_PERIOD_SECS * 1000, TRHostImpl.STATS_PERIOD_SECS * 10);
    private Average average_announce = Average.getInstance(TRHostImpl.STATS_PERIOD_SECS * 1000, TRHostImpl.STATS_PERIOD_SECS * 10);
    private Average average_scrape = Average.getInstance(TRHostImpl.STATS_PERIOD_SECS * 1000, TRHostImpl.STATS_PERIOD_SECS * 10);

    private boolean disable_reply_caching;

    private HashMap data;

    protected AEMonitor this_mon = new AEMonitor("TRHostTorrentHost");

    protected TRHostTorrentHostImpl(TRHostImpl _host, TRTrackerServer _server, TOTorrent _torrent, int _port, long _date_added) {
        host = _host;
        server = _server;
        torrent = _torrent;
        port = _port;
        date_added = _date_added;
    }

    public int getPort() {
        return (port);
    }

    public void start() {
        // there's a potential deadlock situation if we call the server while holding
        // the torrent lock, as the server then calls back to the host and we get
        // a torrent -> host monitor chain. We already have a host->torrent chain.
        // easiest solution is to delegate call to the host, which will grab the host
        // monitor and then call back out to startSupport. Hence the chain is in the
        // right direction

        host.startTorrent(this);
    }

    protected void startSupport() {
        try {
            this_mon.enter();

            // System.out.println( "TRHostTorrentHostImpl::start");

            status = TS_STARTED;

            server_torrent = server.permit("", torrent.getHash(), true);

            if (disable_reply_caching) {

                server_torrent.disableCaching();
            }

        } catch (Throwable e) {

            Debug.printStackTrace(e);

        } finally {

            this_mon.exit();
        }

        host.hostTorrentStateChange(this);
    }

    public void stop() {
        host.stopTorrent(this);
    }

    protected void stopSupport() {
        try {
            this_mon.enter();

            // System.out.println( "TRHostTorrentHostImpl::stop");

            status = TS_STOPPED;

            server.deny(torrent.getHash(), true);

            TRTrackerServerTorrent st = server_torrent;

            TRTrackerServerTorrentStats torrent_stats = st == null ? null : st.getStats();

            if (torrent_stats != null) {

                sos_uploaded = sos_uploaded + torrent_stats.getUploaded();
                sos_downloaded = sos_downloaded + torrent_stats.getDownloaded();
                sos_bytes_in = sos_bytes_in + torrent_stats.getBytesIn();
                sos_bytes_out = sos_bytes_out + torrent_stats.getBytesOut();
                sos_announce = sos_announce + torrent_stats.getAnnounceCount();
                sos_scrape = sos_scrape + torrent_stats.getScrapeCount();
                sos_complete = sos_complete + torrent_stats.getCompletedCount();

                torrent_stats = null;
            }

            last_uploaded = 0;
            last_downloaded = 0;
            last_bytes_in = 0;
            last_bytes_out = 0;
            last_announce = 0;
            last_scrape = 0;

        } catch (Throwable e) {

            Debug.printStackTrace(e);

        } finally {

            this_mon.exit();
        }

        host.hostTorrentStateChange(this);
    }

    public void remove()

    throws TRHostTorrentRemovalVetoException {
        canBeRemoved();

        stop();

        host.remove(this);
    }

    public boolean canBeRemoved()

    throws TRHostTorrentRemovalVetoException {
        ArrayList listeners_copy;

        try {
            this_mon.enter();

            listeners_copy = new ArrayList(removal_listeners);

        } finally {

            this_mon.exit();
        }

        for (int i = 0; i < listeners_copy.size(); i++) {

            ((TRHostTorrentWillBeRemovedListener) listeners_copy.get(i)).torrentWillBeRemoved(this);
        }

        return (true);
    }

    public int getStatus() {
        return (status);
    }

    public boolean isPersistent() {
        return (persistent);
    }

    protected void setPersistent(boolean _persistent) {
        persistent = _persistent;
    }

    public boolean isPassive() {
        return (passive);
    }

    public void setPassive(boolean b) {
        passive = b;
    }

    public long getDateAdded() {
        return (date_added);
    }

    public TOTorrent getTorrent() {
        return (torrent);
    }

    protected void setTorrent(TOTorrent _torrent) {
        torrent = _torrent;
    }

    public TRTrackerServerTorrent getTrackerTorrent() {
        return (server_torrent);
    }

    public TRHostPeer[] getPeers() {
        try {

            TRTrackerServerPeer[] peers = server.getPeers(torrent.getHash());

            if (peers != null) {

                TRHostPeer[] res = new TRHostPeer[peers.length];

                for (int i = 0; i < peers.length; i++) {

                    res[i] = new TRHostPeerHostImpl(peers[i]);
                }

                return (res);
            }
        } catch (TOTorrentException e) {

            Debug.printStackTrace(e);
        }

        return (new TRHostPeer[0]);
    }

    protected TRTrackerServerTorrentStats getStats() {
        TRTrackerServerTorrent st = server_torrent;

        if (st != null) {

            return (st.getStats());
        }

        return (null);
    }

    protected void setStartOfDayValues(long _date_added, long completed, long announces, long scrapes, long uploaded, long downloaded,
            long bytes_in, long bytes_out) {
        date_added = _date_added;
        sos_complete = completed;
        sos_announce = announces;
        sos_scrape = scrapes;
        sos_uploaded = uploaded;
        sos_downloaded = downloaded;
        sos_bytes_in = bytes_in;
        sos_bytes_out = bytes_out;
    }

    public int getSeedCount() {
        TRTrackerServerTorrentStats stats = getStats();

        if (stats != null) {

            return (stats.getSeedCount());
        }

        return (0);
    }

    public int getLeecherCount() {
        TRTrackerServerTorrentStats stats = getStats();

        if (stats != null) {

            return (stats.getLeecherCount());
        }

        return (0);
    }

    public int getBadNATCount() {
        TRTrackerServerTorrentStats stats = getStats();

        if (stats != null) {

            return (stats.getBadNATPeerCount());
        }

        return (0);
    }

    protected void updateStats() {
        TRTrackerServerTorrentStats stats = getStats();

        if (stats != null) {

            long current_uploaded = stats.getUploaded();

            long ul_diff = current_uploaded - last_uploaded;

            if (ul_diff < 0) {

                ul_diff = 0;
            }

            average_uploaded.addValue(ul_diff);

            last_uploaded = current_uploaded;

            // downloaded

            long current_downloaded = stats.getDownloaded();

            long dl_diff = current_downloaded - last_downloaded;

            if (dl_diff < 0) {

                dl_diff = 0;
            }

            average_downloaded.addValue(dl_diff);

            last_downloaded = current_downloaded;

            // bytes in

            long current_bytes_in = stats.getBytesIn();

            long bi_diff = current_bytes_in - last_bytes_in;

            if (bi_diff < 0) {

                bi_diff = 0;
            }

            average_bytes_in.addValue(bi_diff);

            last_bytes_in = current_bytes_in;

            // bytes out

            long current_bytes_out = stats.getBytesOut();

            long bo_diff = current_bytes_out - last_bytes_out;

            if (bo_diff < 0) {

                bo_diff = 0;
            }

            average_bytes_out.addValue(bo_diff);

            last_bytes_out = current_bytes_out;

            // announce

            long current_announce = stats.getAnnounceCount();

            long an_diff = current_announce - last_announce;

            if (an_diff < 0) {

                an_diff = 0;
            }

            average_announce.addValue(an_diff);

            last_announce = current_announce;

            // scrape

            long current_scrape = stats.getScrapeCount();

            long sc_diff = current_scrape - last_scrape;

            if (sc_diff < 0) {

                sc_diff = 0;
            }

            average_scrape.addValue(sc_diff);

            last_scrape = current_scrape;
        }
    }

    protected TRTrackerServer getServer() {
        return (server);
    }

    public long getTotalUploaded() {
        TRTrackerServerTorrentStats stats = getStats();

        if (stats != null) {

            return (sos_uploaded + stats.getUploaded());
        }

        return (sos_uploaded);
    }

    public long getTotalDownloaded() {
        TRTrackerServerTorrentStats stats = getStats();

        if (stats != null) {

            return (sos_downloaded + stats.getDownloaded());
        }

        return (sos_downloaded);
    }

    public long getTotalLeft() {
        TRTrackerServerTorrentStats stats = getStats();

        if (stats != null) {

            return (stats.getAmountLeft());
        }

        return (0);
    }

    public long getTotalBytesIn() {
        TRTrackerServerTorrentStats stats = getStats();

        if (stats != null) {

            return (sos_bytes_in + stats.getBytesIn());
        }

        return (sos_bytes_in);
    }

    public long getTotalBytesOut() {
        TRTrackerServerTorrentStats stats = getStats();

        if (stats != null) {

            return (sos_bytes_out + stats.getBytesOut());
        }

        return (sos_bytes_out);
    }

    public long getAnnounceCount() {
        TRTrackerServerTorrentStats stats = getStats();

        if (stats != null) {

            return (sos_announce + stats.getAnnounceCount());
        }

        return (sos_announce);
    }

    public long getScrapeCount() {
        TRTrackerServerTorrentStats stats = getStats();

        if (stats != null) {

            return (sos_scrape + stats.getScrapeCount());
        }

        return (sos_scrape);
    }

    public long getCompletedCount() {
        TRTrackerServerTorrentStats stats = getStats();

        if (stats != null) {

            return (sos_complete + stats.getCompletedCount());
        }

        return (sos_complete);
    }

    // averages

    public long getAverageBytesIn() {
        return (average_bytes_in.getAverage());
    }

    public long getAverageBytesOut() {
        return (average_bytes_out.getAverage());
    }

    public long getAverageUploaded() {
        return (average_uploaded.getAverage());
    }

    public long getAverageDownloaded() {
        return (average_downloaded.getAverage());
    }

    public long getAverageAnnounceCount() {
        return (average_announce.getAverage());
    }

    public long getAverageScrapeCount() {
        return (average_scrape.getAverage());
    }

    public void disableReplyCaching() {
        TRTrackerServerTorrent st = server_torrent;

        disable_reply_caching = true;

        if (st != null) {

            st.disableCaching();
        }
    }

    protected void preProcess(TRHostTorrentRequest req)

    throws TRHostException {
        List listeners_ref = listeners_cow;

        for (int i = 0; i < listeners_ref.size(); i++) {

            try {
                ((TRHostTorrentListener) listeners_ref.get(i)).preProcess(req);

            } catch (TRHostException e) {

                throw (e);

            } catch (Throwable e) {

                Debug.printStackTrace(e);
            }
        }
    }

    protected void postProcess(TRHostTorrentRequest req)

    throws TRHostException {
        List listeners_ref = listeners_cow;

        for (int i = 0; i < listeners_ref.size(); i++) {

            try {
                ((TRHostTorrentListener) listeners_ref.get(i)).postProcess(req);

            } catch (TRHostException e) {

                throw (e);

            } catch (Throwable e) {

                Debug.printStackTrace(e);
            }
        }
    }

    public void addListener(TRHostTorrentListener l) {
        try {
            this_mon.enter();

            List new_listeners = new ArrayList(listeners_cow);

            new_listeners.add(l);

            listeners_cow = new_listeners;

        } finally {

            this_mon.exit();
        }

        host.torrentListenerRegistered();
    }

    public void removeListener(TRHostTorrentListener l) {
        try {
            this_mon.enter();

            List new_listeners = new ArrayList(listeners_cow);

            new_listeners.remove(l);

            listeners_cow = new_listeners;

        } finally {

            this_mon.exit();
        }
    }

    public void addRemovalListener(TRHostTorrentWillBeRemovedListener l) {
        try {
            this_mon.enter();

            removal_listeners.add(l);

        } finally {

            this_mon.exit();
        }
    }

    public void removeRemovalListener(TRHostTorrentWillBeRemovedListener l) {
        try {
            this_mon.enter();

            removal_listeners.remove(l);
        } finally {

            this_mon.exit();
        }
    }

    /** To retreive arbitrary objects against this object. */
    public Object getData(String key) {
        if (data == null)
            return null;
        return data.get(key);
    }

    /** To store arbitrary objects against this object. */
    public void setData(String key, Object value) {
        try {
            this_mon.enter();

            if (data == null) {
                data = new HashMap();
            }
            if (value == null) {
                if (data.containsKey(key))
                    data.remove(key);
            } else {
                data.put(key, value);
            }
        } finally {

            this_mon.exit();
        }
    }
}
